Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / core / ble / src / commonMain / kotlin / org / meshtastic / core / ble / KableMeshtasticRadioProfile.kt

Displaying Raw • Download

core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableMeshtasticRadioProfile.kt 65a1f5ce4d0e4b8eafcd0bbfccafd41dc6ee888a (65a1f5ce) Text, 7.05 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.ble

Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.CompletableDeferred
Tff7b72import T7ee787kotlinx.coroutines.channels.BufferOverflow
Tff7b72import T7ee787kotlinx.coroutines.delay
Tff7b72import T7ee787kotlinx.coroutines.flow.Flow
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableSharedFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.catch
Tff7b72import T7ee787kotlinx.coroutines.flow.channelFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.emitAll
Tff7b72import T7ee787kotlinx.coroutines.flow.flow
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787org.meshtastic.core.ble.MeshtasticBleConstants.FROMNUM_CHARACTERISTIC
Tff7b72import T7ee787org.meshtastic.core.ble.MeshtasticBleConstants.FROMRADIO_CHARACTERISTIC
Tff7b72import T7ee787org.meshtastic.core.ble.MeshtasticBleConstants.LOGRADIO_CHARACTERISTIC
Tff7b72import T7ee787org.meshtastic.core.ble.MeshtasticBleConstants.TORADIO_CHARACTERISTIC
Tff7b72import T7ee787kotlin.time.Duration.Companion.milliseconds

T8b949e/**
* [MeshtasticRadioProfile] implementation using Kable BLE characteristics.
*
* Uses the standard Meshtastic BLE protocol: FROMNUM notifications trigger polling reads on the FROMRADIO
* characteristic. The firmware gates FROMNUM notifications behind `STATE_SEND_PACKETS`, so during the config handshake
* we seed the drain trigger to poll proactively.
*/
Tff7b72class T56d364KableMeshtasticRadioProfileTb4b4b4(Tff7b72private Tff7b72val Te6edf3serviceTb4b4b4: Te6edf3BleServiceTb4b4b4) Tb4b4b4: Te6edf3MeshtasticRadioProfile Tb4b4b4{

Tff7b72private Tff7b72val Te6edf3toRadio Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3TORADIO_CHARACTERISTICTb4b4b4)
Tff7b72private Tff7b72val Te6edf3fromRadioChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3FROMRADIO_CHARACTERISTICTb4b4b4)
Tff7b72private Tff7b72val Te6edf3fromNum Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3FROMNUM_CHARACTERISTICTb4b4b4)
Tff7b72private Tff7b72val Te6edf3logRadioChar Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3characteristicTb4b4b4(Te6edf3LOGRADIO_CHARACTERISTICTb4b4b4)

T8b949e/**
* Cached preferred write type for [toRadio]. Resolved once at construction so the hot send path doesn't have to
* walk the discovered services list on every packet.
*/
Tff7b72private Tff7b72val Te6edf3toRadioWriteTypeTb4b4b4: Te6edf3BleWriteType Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3preferredWriteTypeTb4b4b4(Te6edf3toRadioTb4b4b4)

Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3TRANSIENT_RETRY_DELAY Tff7b72= T79c0ff5T79c0ff0T79c0ff0.Te6edf3milliseconds
Tb4b4b4}

Tff7b72private Tff7b72val Te6edf3subscriptionReady Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)

T8b949e/**
* Latched signal: a single buffered slot collapses bursts of drain triggers into one pending poll. Capacity 1 with
* DROP_OLDEST means we never block writers and never let stale drain requests pile up.
*/
Tff7b72private Tff7b72val Te6edf3triggerDrain Tff7b72=
Te6edf3MutableSharedFlowTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Te6edf3replay Tff7b72= T79c0ff1Tb4b4b4, Te6edf3extraBufferCapacity Tff7b72= T79c0ff1Tb4b4b4, Te6edf3onBufferOverflow Tff7b72= Te6edf3BufferOverflowTb4b4b4.Te6edf3DROP_OLDESTTb4b4b4)

Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffSwallowedExceptionTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72val Te6edf3fromRadioTb4b4b4: Te6edf3FlowTff7b72<Te6edf3ByteArrayTff7b72> Tff7b72= Te6edf3channelFlow Tb4b4b4{
Te6edf3launch Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3serviceTb4b4b4.Te6edf3hasCharacteristicTb4b4b4(Te6edf3fromNumTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3service
Tb4b4b4.Te6edf3observeTb4b4b4(Te6edf3fromNumTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffFROMNUM CCCD written — notifications enabledTa5d6ff" Tb4b4b4}
Te6edf3subscriptionReadyTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3collect Tb4b4b4{ Te6edf3triggerDrainTb4b4b4.Te6edf3tryEmitTb4b4b4(Tffa657UnitTb4b4b4) Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
T8b949e// Propagate cancellation — don't complete subscription as that would
T8b949e// let setup proceed on a cancelled scope.
Tff7b72throw Te6edf3e
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
T8b949e// Complete subscriptionReady exceptionally so awaitSubscriptionReady()
T8b949e// throws promptly instead of waiting for the transport's 5s timeout.
Te6edf3subscriptionReadyTb4b4b4.Te6edf3completeExceptionallyTb4b4b4(Te6edf3eTb4b4b4)
Tff7b72throw Te6edf3e
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3subscriptionReadyTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Te6edf3triggerDrainTb4b4b4.Te6edf3tryEmitTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3triggerDrainTb4b4b4.Te6edf3collect Tb4b4b4{
Tff7b72var Te6edf3keepReading Tff7b72= Tff7b72true
Tff7b72while Tb4b4b4(Te6edf3keepReadingTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3serviceTb4b4b4.Te6edf3hasCharacteristicTb4b4b4(Te6edf3fromRadioCharTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3keepReading Tff7b72= Tff7b72false
Tff7b72continue
Tb4b4b4}
Tff7b72val Te6edf3packet Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3readTb4b4b4(Te6edf3fromRadioCharTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3packetTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3keepReading Tff7b72= Tff7b72false Tff7b72else Te6edf3sendTb4b4b4(Te6edf3packetTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3e
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
T8b949e// Session-fatal BLE exceptions must propagate so the transport layer detects the
T8b949e// broken session via its .catch handler and triggers reconnection.
Tff7b72if Tb4b4b4(Te6edf3eTb4b4b4.Te6edf3isSessionFatalBleExceptionTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFROMRADIO read hit session-fatal BLE exception — propagating for reconnectTa5d6ff" Tb4b4b4}
Tff7b72throw Te6edf3e
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFROMRADIO read error (transient), pausing before next drain triggerTa5d6ff" Tb4b4b4}
Te6edf3keepReading Tff7b72= Tff7b72false
Te6edf3delayTb4b4b4(Te6edf3TRANSIENT_RETRY_DELAYTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Observes the LOGRADIO characteristic. Session-fatal exceptions propagate to trigger reconnect.
*
* Note: a transient (non-fatal) observation error terminates this flow permanently for the current session. Unlike
* [fromRadio] which has a retry loop, [logRadio] does not recover from transient errors. This is intentional —
* logRadio is diagnostic-only, and the flow is recreated on the next reconnect cycle.
*/
Tff7b72override Tff7b72val Te6edf3logRadioTb4b4b4: Te6edf3FlowTff7b72<Te6edf3ByteArrayTff7b72> Tff7b72= Te6edf3flow Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3serviceTb4b4b4.Te6edf3hasCharacteristicTb4b4b4(Te6edf3logRadioCharTb4b4b4)Tb4b4b4) Tff7b72returnTf0883e@flow
Te6edf3emitAllTb4b4b4(
Te6edf3serviceTb4b4b4.Te6edf3observeTb4b4b4(Te6edf3logRadioCharTb4b4b4)Tb4b4b4.Te6edf3catch Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3e Tff7b72is Te6edf3CancellationExceptionTb4b4b4) Tff7b72throw Te6edf3e
Tff7b72if Tb4b4b4(Te6edf3eTb4b4b4.Te6edf3isSessionFatalBleExceptionTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6fflogRadio observation hit session-fatal BLE exception — propagating for reconnectTa5d6ff" Tb4b4b4}
Tff7b72throw Te6edf3e
Tb4b4b4}
T8b949e// logRadio is optional — log at debug for diagnostics but don't surface to callers.
Te6edf3LoggerTb4b4b4.Te6edf3dTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6fflogRadio observation failure suppressedTa5d6ff" Tb4b4b4}
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffsendToRadioTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3ByteArrayTb4b4b4) Tb4b4b4{
Te6edf3serviceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3toRadioTb4b4b4, Te6edf3packetTb4b4b4, Te6edf3toRadioWriteTypeTb4b4b4)
Te6edf3triggerDrainTb4b4b4.Te6edf3tryEmitTb4b4b4(Tffa657UnitTb4b4b4)
Tb4b4b4}

Tff7b72override Tff7b72fun Td2a8ffrequestDrainTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3triggerDrainTb4b4b4.Te6edf3tryEmitTb4b4b4(Tffa657UnitTb4b4b4)
Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffawaitSubscriptionReadyTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3subscriptionReadyTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.05s